Skip to content

chore: sync fork with upstream (v2.5.6, #338–#456) - #83

Merged
cte13 merged 18 commits into
mainfrom
chore/upstream-sync-6
Oct 24, 2026
Merged

cte13 merged 18 commits into
mainfrom
chore/upstream-sync-6

Conversation

@cte13

@cte13 cte13 commented Oct 24, 2026

Copy link
Copy Markdown
Owner

⚠️ Merge with "Create a merge commit", not squash. Squashing breaks ancestry with upstream and leaves phantom "commits behind".

Summary

Brings the fork up to chattymin/PokeTokenBar main (v2.5.6 plus everything merged since, chattymin#338–chattymin#456; 17 upstream commits). After merging, the fork is 0 commits behind upstream.

Upstream highlights: alternate forms for Gen 1–5 species (chattymin#376), incremental Claude session-log parsing instead of re-reading whole files (chattymin#338), Cursor usage and limits sharing refreshed credentials (chattymin#455), no empty Settings windows on launch/reopen (chattymin#448), sprite animations recovering after failed downloads (chattymin#447), Pokédex Back returning to the viewed page and catch-log row (chattymin#435), collapsed backup history (chattymin#446), a labeled recap control (chattymin#432), repeated Rare Candy grants from volatile reset timestamps (chattymin#375), and a corrected Sonnet 5.5 cache-read price.

Conflict resolutions (7 files) and adaptations to fork-only code

Area Resolution
Sonnet 5.5 price, locked ISO8601Parser The fork keeps ModelPricing and ISO8601Parser in PokeTokenBarShared (the iPhone ledger uses them), so upstream's changes are ported there. The Mac keeps its typealiases. The iPhone's own cost estimates get the price fix too.
Empty Settings windows (chattymin#448) Adopted upstream's _EmptyScene(). With no Settings scene there's no system ⌘, item for the fork to retarget, so SettingsMenuItem.install now adds the fork's own app-menu ⌘, item (or retargets one if the system made it). ⌘, still opens Settings inside the popover. The SettingsSceneBridge fallback is unreachable without the scene and is removed. New tests cover insert, no-duplicate retarget, and no app menu.
Antigravity transient refresh failure (upstream v2.5.6) The fork had the same gap: on the manual Keychain path, a temporary grant failure returned the expired token, quota answered 401, and invalidate() deleted the only refresh token. Ported onto the fork's AntigravityRefreshOutcome model: the credential is cached before the grant, and an expired credential with a .transient outcome throws AntigravityTokenRefreshError.transient (shown as the generic refresh message). Upstream's tests depend on MockOAuthURLProtocol/urlSession:, which the fork doesn't have, so they were rewritten onto the injected tokenRefresher. The fork folds timeout/429/5xx into .transient, so one recovery test covers all three.
Generic forms (chattymin#376) Fork-only iPhone export code used the removed UnownForm API. The phone payload stays Unown-only for now and is mapped from the new PokemonForm API; a new test ensures other species' forms (e.g. Sky Shaymin) aren't sent as Unown letters. Showing all forms on the iPhone is a follow-up.
README.md, SettingsView state, defect-log.md Kept both sides.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other: upstream sync (merge commit)

UI changes

The visible changes all come from upstream PRs, which were reviewed there with these images. The fork's merge resolution doesn't change their layout. Images are reused from the upstream PRs and labeled with their source.

Before After
Before — Pokédex grid, app screenshot with synthetic save, upstream #376 After — Pokédex grid with forms, app screenshot with synthetic save, upstream #376
Before — Pokédex detail, upstream #376 After — Pokédex detail with form picker, upstream #376
Before — Home, upstream #376 After — Home showing a form, upstream #376
Before — recap control, sample-data render, upstream #432 After — labeled recap control, sample-data render, upstream #432
Before — backup history, sample-data render, upstream #446 After — collapsed backup history, sample-data render, upstream #446
Before — Back returns to page 1, sample-data render, upstream #435 After — Back returns to the viewed page, sample-data render, upstream #435
Before — static sprite after failed download, illustration, upstream #447 After — animation recovers, illustration, upstream #447
Before — empty Settings window, illustration / mockup, upstream #448 After — no standalone window, illustration / mockup, upstream #448

More upstream images: catch-log row restore and the German/English cost rows in #435 and #432, expanded backup history in #446.

Fork-specific: ⌘, behaves as before (opens Settings in the popover), now through the fork's own app-menu item. Nothing about it looks different, so there's no image. The iPhone app's UI is unchanged in this PR.

Validation

  • swift build passes; the iOS app (PokeTokenBariOS scheme, simulator) builds.
  • ./scripts/test-gate.sh: 1,780 tests, 19 skipped, 1 failure: UsageStoreTests.testKeychainDisabledRecordsNoHistory, a known local-only failure (a saved session key on this machine leaks in; it passes in CI). The full run skipped KeychainAutoPathTests and IncrementalStoreScanTests, which stall locally on Keychain prompts; CI runs them.
  • gitleaks detect over origin/main..HEAD: no leaks.

Checklist

  • swift build and swift test pass locally (build passes; one known local-only failure, see Validation)
  • PR title follows Conventional Commits and title/description are written in English
  • UI changes include embedded images (before/after for existing screens; image of a new screen), or there is no visible UI change (upstream PR images reused)
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

🤖 Generated with Claude Code

KillianHzr and others added 18 commits October 15, 2026 15:35
chattymin#375)

Normalize candy window epoch timestamps and require window-scale advancement (>= 30m for session, >= 24h for weekly) before rearming candy grants. This prevents Anthropic API microsecond fluctuations, Codex ±1s timestamp jitter, and rolling window drift from repeatedly rearming at 100% utilization.
…after Back (chattymin#435)

* test(collection): add a pure page lookup for the Pokédex grid

DexScrollPager.page(containing:in:pageSize:) returns the page that shows
a species, or 0 when it is nil or filtered out. Not wired up yet; the
next commit uses it to return to the viewed page after Back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(collection): return to the viewed Pokédex page after Back

The detail page replaces the grid, so Back rebuilt DexGridView and its
page state started over at page 1. CollectionView now remembers the
species whose detail page was closed and the rebuilt grid starts with it
selected, deriving the page from that selection. Opening a species from
a Home sprite now also lands on its page on Back. The anchor is used
once, so a later rebuild (segment switch) still starts on page 1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(collection): keep the catch log row in view after Back

The catch log sits in the same branch the detail page replaces, so Back
scrolled it to the top. The row that opened the detail page is now
scrolled back to the center. The scroll waits one turn after the log
appears: before the first layout the lazy rows above it are estimated
and the scroll stopped short of the row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: log the detail-page remount that reset the Pokédex page

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: show Pokédex and catch log Back comparison

Sample-data renders of the real CollectionView for the PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(collection): correct the catch log scroll after rows are measured

CI showed the restored log stopping a row short of the row that opened
the detail page. The first scrollTo places the target with estimated
heights for the lazy rows; once the rows it reveals are measured they
push the target off its spot. Scroll again after that layout pass. A
single scroll fails the test locally too.

The tests also retry a press until the screen changes: right after a
transition the view tree can still hit-test the previous screen, which
made a Back press miss on CI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: record the catch log scroll CI finding

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(collection): set the grid return anchor only when Back lands on the grid

Back always stored the closed species as the grid's return anchor, but
only the grid's onAppear clears it. When Back returned to the catch log
(the detail page was opened from a log row, or from a Home sprite while
the log segment was selected), the anchor stayed, and the next switch
to the Pokédex segment opened on that species' page with it selected
instead of page 1.

testBackToTheLogLeavesTheGridOnItsFirstPage covers that path and fails
when the anchor is stored unconditionally again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(collection): reopen the Pokédex page after Back without selecting the species

The grid found its return page by starting with the closed species as
its selection. That brought back the selection border and the footer
line, which nothing had shown since the detail page started replacing
the grid (chattymin#264), and made the grid behave unlike the catch log, which
only scrolls back to the row.

DexGridView now keeps the anchor in its own state and uses it only to
pick the page. testBackFromDetailReturnsToThePageOfThatSpecies asserts
that the footer does not name the species and fails when the anchor
selects it again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs: re-render the Pokédex Back comparison without the selection

Sample-data render of the real CollectionView after Back: page 2 with no
species selected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Generalize the Unown letter forms (chattymin#288) to every generation 1-5 species
whose forms all have an animated Black/White sprite: Castform, Deoxys,
Burmy/Wormadam, Cherrim, Shellos/Gastrodon, Rotom, Giratina, Shaymin,
Arceus, Basculin, Darmanitan, Deerling/Sawsbuck, the forces of nature,
Kyurem, Keldeo, Meloetta and Genesect.

The form belongs to the individual, is drawn once for the whole line and
is resolved per species. Saves use form/pendingForm/representativeForm and
still read the former Unown keys.
…n#446)

* fix(settings): collapse automatic backup history by default

* test(settings): reveal snapshot disclosure before locating it

* test(settings): stabilize native snapshot capture and disclosure clicks

* fix(settings): use a native button for backup history

* test(settings): capture snapshot history across native viewports

* test(settings): recognize native snapshot renders in viewport strips
Correct Sonnet 5.5 cache-read estimates across persisted usage caches. Carry the current form into backup summaries and thumbnails. Preserve Antigravity refresh credentials after transient grant failures. Explain default-form metadata beside alternate appearances, with native rendering and focused regression guards.
…min#448)

Retain SwiftUI App startup and replace the unused Settings { EmptyView() } scene with _EmptyScene(). Settings remain in the existing popover; startup, menus, activation policy, and popover placement otherwise match main.

Stabilize the Korean chart OCR fixture using exact captions among equally highest-confidence candidates. Preserve diagnostic candidates and CI fixture images.

Validation: final PR build passed; 1,520 local tests excluding window-presenting suites passed with 16 skipped and zero failures. The empty-scene entry point compiles against SDK14.5 and26.5 targeting macOS14. Full CI passed: 1,560 tests, 16 skipped, zero failures, 93.80% logic-core line coverage; secret scan passed. Local app replacement is deferred to avoid desktop interruption.
…ole files (chattymin#338)

On an account with a large Claude Code history (~2GB of session logs in
the current month, single files up to 274MB) the cold scan pegged a core
for over a minute without finishing, and because the blob cache is keyed
on whole-file mtime+size, the active session file was re-parsed from byte
zero on every refresh.

Three changes:
- ISO8601Parser reuses one lock-guarded formatter pair instead of
  allocating an ISO8601DateFormatter per line (the dominant sampled cost).
- parseClaudeFile scans lines at the byte level via Data/FileHandle rather
  than materializing the file as a String and splitting into Substrings.
- Blob records a per-file byte offset; when a file has only grown, only
  the appended tail is parsed and merged. Same-size or shrunk files are
  re-read from the start. Older cache files without the key still load.

Measured on the history above: cold start ~15s then idle (was 60s+ and
never completed), steady-state refresh stays at 0% CPU.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick.Park <pdm001125@naver.com>
* fix: parse Claude session logs incrementally instead of re-reading whole files

On an account with a large Claude Code history (~2GB of session logs in
the current month, single files up to 274MB) the cold scan pegged a core
for over a minute without finishing, and because the blob cache is keyed
on whole-file mtime+size, the active session file was re-parsed from byte
zero on every refresh.

Three changes:
- ISO8601Parser reuses one lock-guarded formatter pair instead of
  allocating an ISO8601DateFormatter per line (the dominant sampled cost).
- parseClaudeFile scans lines at the byte level via Data/FileHandle rather
  than materializing the file as a String and splitting into Substrings.
- Blob records a per-file byte offset; when a file has only grown, only
  the appended tail is parsed and merged. Same-size or shrunk files are
  re-read from the start. Older cache files without the key still load.

Measured on the history above: cold start ~15s then idle (was 60s+ and
never completed), steady-state refresh stays at 0% CPU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(home): label the usage recap control

The chart icon was the only way into week, month, and year recaps, and it did not look like a button. Show a short localized label inside a light capsule.

* fix(home): preserve period values beside the recap label

* test(home): match OCR languages to period fixtures

---------

Co-authored-by: notcadenwhitt-stack <notcadenwhitt@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick.Park <pdm001125@naver.com>
Use an unambiguous word for the oldest catch while retaining exact OCR,
click, Back navigation and scroll reset assertions. Adapt the fixture
repair from c7c2d7a and record the CI failure in the defect log.

Co-authored-by: 서민수 <sms@bbros.kr>
Co-authored-by: Codex <noreply@openai.com>
Merge chattymin/PokeTokenBar main (17 commits) into the fork.

Conflict resolutions that adapt upstream changes to fork-only code:
- Sonnet 5.5 cache-read price and the locked ISO8601 parser ported into PokeTokenBarShared,
  where the fork keeps ModelPricing and ISO8601Parser; the Mac keeps its typealiases.
- Empty Settings windows (chattymin#448): adopted _EmptyScene(). Without a Settings scene there is no
  system ⌘, item, so SettingsMenuItem.install adds the fork's own app-menu item (retargets an
  existing one if present). The unreachable SettingsSceneBridge fallback is removed.
- Antigravity transient refresh failure: ported upstream's fix onto the fork's
  AntigravityRefreshOutcome model. An expired Keychain credential whose grant fails temporarily
  now throws AntigravityTokenRefreshError.transient instead of returning the dead token (which
  401'd and invalidated the only refresh token). Upstream's tests rewritten onto tokenRefresher.
- Generic Pokémon forms (chattymin#376): the iPhone payload stays Unown-only; the export maps the new
  PokemonForm API onto it and no longer risks sending other species' forms as Unown letters.
- README, SettingsView state and defect-log: kept both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cte13
cte13 merged commit 9ae6aa2 into main Oct 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants